﻿#region License
/**	
The MIT License (MIT)

Copyright (c) 2013 Philipp Schillinger, Michael Alekseew, Andreas Braun, Steeven Zeiß

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 
 **/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;

namespace IndoorLocalization {

	/// <summary>
	/// Factory and functional support for network messages.
	/// Each message is byte array that is structured in defined way
	/// and can be sent directly via the network.
	/// The messages are structured as documented in the corresponding protocol document.
	/// </summary>
	/*static*/ class Message {

		/// <summary>
		/// The type of the network message.
		/// </summary>
		public enum Type {
			UNKNOWN,
			Welcome,		// 0
			WelcomeCallback,// 5
			Goodbye,		// 1
			Entities,		// 2
			Rays,			// 3
			MergerChanged	// 4
		};

		/// <summary>
		/// Determines the type of the message.
		/// </summary>
		/// <param name="message">The message as byte array.</param>
		/// <returns>The type of the message.</returns>
		public static Type GetType(byte[] message) {
			switch (message[0]) {
				case 0: return Type.Welcome;
				case 5: return Type.WelcomeCallback;
				case 1: return Type.Goodbye;
				case 2: return Type.Entities;
				case 3: return Type.Rays;
				case 4: return Type.MergerChanged;
				default: return Type.UNKNOWN;
			}
		}

		/// <summary>
		/// Determines the sender of the message.
		/// </summary>
		/// <param name="message">The message as byte array.</param>
		/// <returns>The ID of the sender of the message.</returns>
		public static byte GetSenderID(byte[] message) {
			return message[1];
		}


		/// <summary>
		/// Creates a new welcome message that is broadcasted when an application starts its network.
		/// </summary>
		/// <param name="senderID">The ID of the sender of the message.</param>
		/// <param name="senderIP">The IP adress of the sender that can be used for P2P communication.</param>
		/// <param name="mergers">A list of all available mergers.</param>
		/// <returns>The message as a byte array.</returns>
		public static byte[] CreateWelcomeMessage(byte senderID, IPAddress senderIP, List<SensorMerger> mergers) {
			// create a string of all merger names
			String mergerList = "";
			foreach(SensorMerger merger in mergers) mergerList += merger.Name+",";
			mergerList = mergerList.Remove(mergerList.Length-1);
			int mergerListLength = mergerList.Length * sizeof(char);

			// create message
			byte[] message = new byte[6 + mergerListLength];
			message[0] = 0;
			message[1] = senderID;
			Buffer.BlockCopy(senderIP.GetAddressBytes(), 0, message, 2, 4);
			Buffer.BlockCopy(mergerList.ToCharArray(), 0, message, 6, mergerListLength);
			
			return message;
		}
		/// <summary>
		/// Creates a new goodbye message that notifies the other applications that this application
		/// stops its network.
		/// </summary>
		/// <param name="senderID">The ID of the sender of the message.</param>
		/// <returns>The message as a byte array.</returns>
		public static byte[] CreateGoodbyeMessage(byte senderID) {
			byte[] message = new byte[] {
				1,
				senderID
			};
			return message;
		}
		/// <summary>
		/// Creates a message to synchronize all found entities.
		/// </summary>
		/// <param name="senderID">The ID of the sender of the message.</param>
		/// <param name="entities">A list of all entities to be synchronized.</param>
		/// <returns>The message as a byte array.</returns>
		public static byte[] CreateEntitiesMessage(byte senderID, List<Entity> entities) {
			byte[] message = new byte[2 + entities.Count * 16];
			message[0] = 2;
			message[1] = senderID;
			for (int i = 0; i < entities.Count; i++) {
				int index = i * 16 + 2;
				byte[] id = BitConverter.GetBytes(entities[i].ID);
				byte[] x = BitConverter.GetBytes(entities[i].X);
				byte[] y = BitConverter.GetBytes(entities[i].Y);
				byte[] r = BitConverter.GetBytes(entities[i].Reliability);
				Buffer.BlockCopy(id, 0, message, index, 4);
				Buffer.BlockCopy(x, 0, message, index + 4, 4);
				Buffer.BlockCopy(y, 0, message, index + 8, 4);
				Buffer.BlockCopy(r, 0, message, index + 12, 4);
			}
			return message;
		}
		/// <summary>
		/// Creates a P2P message to send rays to a remote merger.
		/// </summary>
		/// <param name="senderID">The ID of the sender of the message.</param>
		/// <param name="mergerName">The name of the remote merger.</param>
		/// <param name="camGuid">The GUID of the camera that created these rays.</param>
		/// <param name="rays">A list of all rays to send.</param>
		/// <returns>The message as a byte array.</returns>
		public static byte[] CreateRaysMessage(byte senderID, String mergerName, Guid camGuid, List<SensorRay> rays) {
			byte mergerNameLength = (byte)(mergerName.Length * sizeof(char));
			byte[] message = new byte[3 + mergerNameLength + 16 + rays.Count * 12];

			message[0] = 3;
			message[1] = senderID;
			message[2] = mergerNameLength;
			Buffer.BlockCopy(mergerName.ToCharArray(), 0, message, 3, mergerNameLength);
			Buffer.BlockCopy(camGuid.ToByteArray(), 0, message, 3 + mergerNameLength, 16);

			for (int i = 0; i < rays.Count; i++) {
				int index = i * 12 + 3 + mergerNameLength + 16;
				byte[] x = BitConverter.GetBytes(rays[i].X);
				byte[] y = BitConverter.GetBytes(rays[i].Y);
				byte[] angle = BitConverter.GetBytes((int)rays[i].Angle);
				Buffer.BlockCopy(x, 0, message, index, 4);
				Buffer.BlockCopy(y, 0, message, index + 4, 4);
				Buffer.BlockCopy(angle, 0, message, index + 8, 4);
			}

			return message;
		}
		/// <summary>
		/// Creates a broadcast message to notify all applications that a merger has been
		/// added or removed depending whether the merger already has existed or not.
		/// </summary>
		/// <param name="senderID">The ID of the sender of the message.</param>
		/// <param name="mergerName">The name of the changed merger.</param>
		/// <returns>The message as a byte array.</returns>
		public static byte[] CreateMergerChangedMessage(byte senderID, String mergerName) {
			int mergerNameLength = mergerName.Length * sizeof(char);
			byte[] message = new byte[2 + mergerNameLength];

			message[0] = 4;
			message[1] = senderID;
			Buffer.BlockCopy(mergerName.ToCharArray(), 0, message, 2, mergerNameLength);

			return message;
		}
		/// <summary>
		/// Creates a new welcome callback message that is send P2P when a welcome message is received.
		/// </summary>
		/// <param name="senderID">The ID of the sender of the message.</param>
		/// <param name="senderIP">The IP adress of the sender that can be used for P2P communication.</param>
		/// <param name="mergers">A list of all available mergers.</param>
		/// <returns>The message as a byte array.</returns>
		public static byte[] CreateWelcomeCallbackMessage(byte senderID, IPAddress senderIP, List<SensorMerger> mergers) {
			byte[] message = CreateWelcomeMessage(senderID, senderIP, mergers);
			message[0] = 5;
			return message;
		}


		/// <summary>
		/// Decodes a new welcome message that is broadcasted when an application starts its network.
		/// </summary>
		/// <param name="message">A byte array containing the message.</param>
		/// <param name="senderIP">A reference to where the decoded IP adress of the sender should be stored.</param>
		/// <param name="mergers">A reference to where the decoded list of mergers should be stored.</param>
		public static void DecodeWelcomeMessage(byte[] message, ref IPAddress senderIP, ref List<SensorMerger> mergers) {
			if (GetType(message) != Type.Welcome && GetType(message) != Type.WelcomeCallback) return;

			byte senderID = message[1];

			// decode sender IP adress
			byte[] ipBytes = new byte[4];
			Buffer.BlockCopy(message, 2, ipBytes, 0, ipBytes.Length);
			senderIP = new IPAddress(ipBytes);

			// decode merger list
			char[] mergerListChars = new char[(message.Length - 6) /  sizeof(char)];
			Buffer.BlockCopy(message, 6, mergerListChars, 0, message.Length-6);
			String mergerListString = new String(mergerListChars);
			String[] mergerListNames = mergerListString.Split(',');
			foreach (String mergerName in mergerListNames)
				mergers.Add(new RemoteMerger(mergerName, senderID));
		}
		/// <summary>
		/// Decodes a message to synchronize all found entities.
		/// </summary>
		/// <param name="message">A byte array containing the message.</param>
		/// <param name="entities">A reference to where the decoded list of entities should be stored.</param>
		public static void DecodeEntitiesMessage(byte[] message, ref List<Entity> entities) {
			if (GetType(message) != Type.Entities) return;
			
			byte sender = message[1];
			for (int i = 0; i < message.Length / 16; i++) {
				int index = i * 16 + 2;
				int id = BitConverter.ToInt32(message, index);
				int x = BitConverter.ToInt32(message, index + 4);
				int y = BitConverter.ToInt32(message, index + 8);
				int r = BitConverter.ToInt32(message, index + 12);
				Entity entity = new Entity(id, new HCoord(x, y), r, sender);

				if (FloorPlan.InBounds(new HCoord(entity.X, entity.Y)))
					entities.Add(entity);
				else
					Program.Warn("Received entity out of bounds (ID: " + sender + ")");
			}
		}
		/// <summary>
		/// Decodes a P2P message to send rays to a remote merger.
		/// </summary>
		/// <param name="message">A byte array containing the message.</param>
		/// <param name="mergerName">A reference to where the decoded name of the local merger should be stored.</param>
		/// <param name="camGuid">A reference to where the decoded GUID of the camera that created these rays should be stored.</param>
		/// <param name="rays">A reference to where the decoded list of all rays should be stored.</param>
		public static void DecodeRaysMessage(byte[] message, ref String mergerName, ref Guid camGuid, ref List<SensorRay> rays) {
			if (GetType(message) != Type.Rays) return;

			// decode merger name
			byte mergerNameLength = message[2];
			char[] mergerNameChars = new char[(mergerNameLength) / sizeof(char)];
			Buffer.BlockCopy(message, 3, mergerNameChars, 0, mergerNameLength);
			mergerName = new String(mergerNameChars);

			// decode guid
			byte[] camGuidBytes = new byte[16];
			Buffer.BlockCopy(message, 3 + mergerNameChars.Length, camGuidBytes, 0, 16);
			camGuid = new Guid(camGuidBytes);

			// decode rays
			int numberOfRays = (message.Length - mergerNameLength - 3 - 16)/12;
			for (int i = 0; i < numberOfRays; i++) {
				int index = i * 12 + mergerNameLength + 3 + 16;
				int x = BitConverter.ToInt32(message, index);
				int y = BitConverter.ToInt32(message, index + 4);
				int a = BitConverter.ToInt32(message, index + 8);
				rays.Add(new SensorRay(new HCoord(x, y), a, camGuid));
			}
		}
		/// <summary>
		/// Decodes a broadcast message to notify all applications that a merger has been
		/// added or removed depending whether the merger already has existed or not.
		/// </summary>
		/// <param name="message">A byte array containing the message.</param>
		/// <param name="mergerName">A reference to where the decoded name of the changed merger should be stored.</param>
		public static void DecodeMergerChangedMessage(byte[] message, ref String mergerName) {
			if (GetType(message) != Type.MergerChanged) return;

			// decode merger name
			int mergerNameLength = message.Length - 2;
			char[] mergerNameChars = new char[(mergerNameLength) / sizeof(char)];
			Buffer.BlockCopy(message, 2, mergerNameChars, 0, mergerNameLength);
			mergerName = new String(mergerNameChars);
		}

	}

}
